#pragma once
#include <string>
#include <iostream>
#include <vector>
#include <mutex>
#include <cassert>
#include <fstream>
#include <memory>
#include "jsoncpp/json/json.h"
#include <ctemplate/template.h>
#include "../comm/httplib.h"
#include "oj_mode.hpp"
#include "../comm/log.hpp"
#include "oj_view.hpp"

// 是控制器,项目的核心模块
// 用于数据和网页的交互
namespace NS_CTRL
{
    using namespace NS_MODEL;
    using namespace httplib;
    NS_MODEL::mod model_; // 提供后台数据
    NS_VIEW::view view_;  // 网页渲染

    class Machine
    {
    public:
        std::string ip; // 编译服务的ip
        int port;       // 编译服务的端口
        uint64_t load;  // 负载
        // std::unique_ptr<std::mutex> mtx;
        std::mutex *mtx;

    public:
        Machine()
            : ip(" "), port(0), load(0), mtx(nullptr)
        {
        }
        ~Machine() {}

        void IncLoad()
        {
            if (mtx)
                mtx->lock();
            ++load;
            if (mtx)
                mtx->unlock();
        }

        void DecLoad()
        {
            if (mtx)
                mtx->lock();
            --load;
            if (mtx)
                mtx->unlock();
        }

        uint64_t Load()
        {
            uint64_t _load = 0;
            if (mtx)
                mtx->lock();
            _load = load;
            if (mtx)
                mtx->unlock();
            return _load;
        }

        void Reset()
        {
            if(mtx) mtx->lock();
            load=0;
            if(mtx) mtx->unlock();
        }
    };

    const std::string server_machine = "./conf/server_machine.conf";
    const std::string ip_port_sep = ":";
    // 负载均衡模块
    // 主机列表，在线的主机，离线的主机
    class LoadBlance
    {
    private:
        // 主机列表
        std::vector<Machine> machines;
        // 所有在线的主机，利用machines的下标当主机编号
        std::vector<int> online;
        std::vector<int> offline;

        std::mutex mtx;

    public:
        LoadBlance()
        {
            assert(LoadConf(server_machine));
            LOG(Info) << "加载" << server_machine << "成功！" << std::endl;
        }

        bool LoadConf(const std::string &_path)
        {
            std::ifstream in(_path);
            if (!in.is_open())
            {
                LOG(Error) << "加载配置文件:" << _path << " 失败." << std::endl;
                return false;
            }
            std::string line;
            while (getline(in, line))
            {
                std::vector<std::string> tokens;
                StringUntil::SplitString(line, &tokens, ip_port_sep);
                if (tokens.size() != 2)
                {
                    LOG(Warning) << "切分 " << line << " 失败!" << std::endl;
                    continue;
                }

                Machine m;
                m.ip = tokens[0];
                m.port = atoi(tokens[1].c_str());
                m.load = 0;
                m.mtx = new std::mutex;

                online.push_back(machines.size());
                machines.push_back(m);
            }
            in.close();
            return true;
        }

        // id:输出型参数
        // m: 输出型参数
        bool SmartChooic(int *id, Machine **m)
        {
            mtx.lock();
            // 负载均衡的设计
            // 轮询，选择负载最小的机器
            int online_nums = online.size();
            if (online.size() == 0)
            {
                LOG(Error) << "所有的主机都挂掉了,请尽快恢复" << std::endl;
                mtx.unlock();
                return false;
            }

            *id = online[0];
            *m = &machines[online[0]];
            uint64_t min_load_machine = machines[online[0]].Load();
            for (int i = 0; i < online_nums; i++)
            {
                uint64_t curr_load = machines[online[i]].Load();
                if (min_load_machine > curr_load)
                {
                    min_load_machine = curr_load;
                    *id = online[i];
                    *m = &machines[online[i]];
                }
            }
            mtx.unlock();
            return true;
        }

        void ShowMachines()
        {
            mtx.lock();
            std::cout << "online_machines :";
            for (auto &e : online)
            {
                std::cout << e << " ";
            }
            std::cout << std::endl;
            std::cout << "offline_machines :";
            for (auto &e : offline)
            {
                std::cout << e << " ";
            }
            std::cout << std::endl;
            mtx.unlock();
        }

        void OffMachine(int id)
        {
            mtx.lock();
            for(auto iter=online.begin();iter!=online.end();++iter)
            {
                if(*iter==id)
                {
                    machines[id].Reset();
                    online.erase(iter);
                    offline.push_back(id);
                    break;
                }
            }
            mtx.unlock();
        }

        //一键上线所有的主机
        void OnMachines()
        {
            mtx.lock();
            online.insert(online.begin(),offline.begin(),offline.end());
            offline.erase(offline.begin(),offline.end());
            mtx.unlock();
        }
    };
    class contral
    {
    private:
        NS_MODEL::mod model_;
        LoadBlance load_blance;

    public:
        contral(){};

        ~contral(){};

    public:
        // 渲染所有题目到网页上
        bool AllQuetsion(std::string *html)
        {
            vector<struct Question> all;
            if (model_.GetAllQustions(&all))
            {
                sort(all.begin(),all.end(),[](const struct Question &q1,const struct Question &q2 ){
                    return atoi(q1.number.c_str())<atoi(q2.number.c_str());
                });

                view_.ExpandAllHtml(all, html);
                return true;
            }
            else
            {
                *html = "获取题目失败";
            }
            return false;
        }

        bool OneQuestion(const std::string &number, std::string *html)
        {
            struct Question question;
            if (model_.GetQuestion(number, &question))
            {
                view_.ExpandOneHtml(question, html);
                return true;
            }
            else
            {
                *html = "题目序号:" + number + "不存在!";
            }
            return false;
        }

        // in_json
        // code:#include...
        // input:用户代码的提交，不处理
        // cup_limit:
        // mem_limit:

        // 输入为题目序号,用户提交的json,输出判题结果json
        bool Judge(const std::string &number, const std::string &in_json, std::string *out_json)
        {
            // 1.获取指定题目细节
            struct Question q;
            model_.GetQuestion(number, &q);

            // 反序列化
            Json::Reader reader;
            Json::Value in_value;
            reader.parse(in_json, in_value);
            std::string code = in_value["code"].asString();

            // 2.拼接+序列化
            Json::Value compiler_server;
            compiler_server["input"] = in_value["input"].asString();
            compiler_server["code"] = code + "\n" + q.tail;
            compiler_server["cpu_limit"] = q.cpu_limit;
            compiler_server["mem_limit"] = q.mem_limit;

            Json::FastWriter writer;
            std::string compiler_string = writer.write(compiler_server);

            // 3.选择负载最小的主机
            while (true)
            {
                Machine *m=nullptr;
                int id;
                if (!load_blance.SmartChooic(&id, &m))
                {
                    LOG(Fatal) << "选择主机失败!" << std::endl;
                    break;
                }
                LOG(Info) << "选择主机:" << id << "成功!" << std::endl;

                // 向编译服务发起请求
                Client cli(m->ip, m->port);
                m->IncLoad();
                LOG(Info) << "选择主机:" << m->ip << ":" << m->port << " 当前主机负载是:" << m->Load() << std::endl;
                if (auto ret = cli.Post("/compile_run", compiler_string, "application/json;charset=utf-8"))
                {
                    // 编译成功
                    if (ret->status == 200)
                    {
                        *out_json = ret->body;
                        LOG(Info) << "编译请求成功！当前主机：" << m->ip << ":" << m->port << " 当前主机负载是:" << m->Load() << std::endl;
                        m->DecLoad();
                        break;
                    }
                    m->DecLoad();
                }
                else
                {
                    LOG(Warning) << "请求主机编译失败" << m->ip << ":" << m->port << " 当前主机负载是:" << m->Load() << std::endl;
                    load_blance.OffMachine(id);
                    load_blance.ShowMachines();
                }
            }

            return false;
        }

        void RecoveryMachines()
        {
            load_blance.OnMachines();
        }
    };
}